package com.hjm.friend.service.user.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.hjm.common.core.constants.CacheConstants;
import com.hjm.common.core.constants.Constants;
import com.hjm.common.core.constants.SecurityConstants;
import com.hjm.common.core.domain.Result;
import com.hjm.common.core.domain.suser.LoginUser;
import com.hjm.common.core.domain.vo.LoginUserVO;
import com.hjm.common.core.enums.ResultCode;
import com.hjm.common.core.enums.UserIdentity;
import com.hjm.common.core.enums.UserStatus;
import com.hjm.common.core.utils.ThreadLocalUtils;
import com.hjm.message.service.AliSmsService;
import com.hjm.friend.domain.user.User;
import com.hjm.friend.domain.user.vo.UserDetailVO;
import com.hjm.friend.manage.UserCacheManager;
import com.hjm.friend.mapper.user.UserMapper;
import com.hjm.friend.service.user.UserService;
import com.hjm.redis.service.RedisService;
import com.hjm.security.exception.ServiceException;
import com.hjm.friend.domain.user.dto.UserDTO;
import com.hjm.security.service.TokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    TokenService tokenService;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    AliSmsService aliSmsService;

    @Autowired
    RedisService redisService;

    @Autowired
    UserCacheManager userCacheManager;

    @Value("${jwt.secret}")
    String secret;

    @Value("${sms.code-expiration:5}")
    private long phoneCodeExpiration;


    @Value("${sms.send-limit:3}")
    private Integer sendLimit;

    @Value("${sms.is-send:}")
    private Boolean isSend;    // 用来方便测试：不真正发送短信

    @Value("${file.oss.downUrl}")
    String downUrl;

    @Override
    public Boolean sendMsg(UserDTO userDTO) {
        // 首先验证手机号是否正确
        if(!checkPhone(userDTO.getPhone())){
            throw new ServiceException(ResultCode.FAILED_PHONE_FORMAT);
        }

        String phoneCodeKey = getPhoneCodeKey(userDTO.getPhone());
        // 60 s 内不能重复发送
        Long expire = redisService.getExpire(phoneCodeKey,TimeUnit.SECONDS);
        if(expire != null && phoneCodeExpiration * 60 -  expire  < 60 ){
            throw new ServiceException(ResultCode.FAILED_CODE_FREQUENT);
        }

        // 限制每天的次数
        String codeTimeKey=getPhoneCodeTimeKey(userDTO.getPhone());
        Long sendTime = redisService.getCacheObject(codeTimeKey,Long.class);
        if(sendTime != null && sendTime >= sendLimit){
            throw  new ServiceException(ResultCode.FAILED_TIME_LIMIT);
        }

        // 开始发送验证码
        String code = isSend ? RandomUtil.randomNumbers(6) : Constants.PHONE_DEFAULT_CODE ;       // 生成 6 位 的随机数
        // 存储到redis 中
        redisService.setCacheObject( phoneCodeKey, code , phoneCodeExpiration, TimeUnit.MINUTES);

        if(isSend){
            boolean b = aliSmsService.sendMobileCode(userDTO.getPhone(), code);
            if(!b){
                throw new ServiceException(ResultCode.FAILED_CODE_SEND);
            }
        }

        // 计数
        redisService.increment(codeTimeKey);

        // 每天第一次 发短信的时候
        if(sendTime == null){
            // 拿到 now 到第二天 的剩余时间
            long seconds= ChronoUnit.SECONDS.between(LocalDateTime.now(),
                    LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0));
            // 设置 过期时间
            redisService.expire(codeTimeKey, seconds, TimeUnit.SECONDS);
        }

        return true;
    }

    @Override
    public String login(String phone, String code) {

        // 新用户  ---  老用户
        // 首先验证 手机号是否正确， 再验证验证码 是否正确
        if(!checkPhone(phone)){
            throw new ServiceException(ResultCode.FAILED_PHONE_FORMAT);
        }

        String phoneCodeKey = getPhoneCodeKey(phone);
        String cacheCode = redisService.getCacheObject(phoneCodeKey, String.class);
        // 先确认发没发送验证码
        if(cacheCode == null ){
            throw new ServiceException(ResultCode.FAILED_CODE_ERROR_SEND);
        }
        // 确定验证码正误
        if(!cacheCode.equals(code)){
            throw new ServiceException(ResultCode.FAILED_CODE_ERROR);
        }

        // 验证码正确之后，再看 是新用户还是老用户
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone, phone));
        if(BeanUtil.isEmpty(user)){
            // 新用户
            user = new User();
            user.setPhone(phone);
            user.setUserAccount(phone);
            user.setPassword("123456");
            user.setStatus(UserStatus.NORMAL.ordinal());
            userMapper.insert(user);
        }

        return tokenService.createToken(user.getUserId(), UserIdentity.ORDINARY.getValue(), user.getNickName(),user.getHeadImage());
    }

    // 登录成功后获取 user 的基本信息
    @Override
    public Result<LoginUserVO> info(String token) {
        LoginUser loginUser = tokenService.getLoginUser(token);
        // 拿不到信息 , 没登录或者登录过期
        if(loginUser == null){
            return Result.fail();
        }

        LoginUserVO loginUserVO = new LoginUserVO();
        loginUserVO.setNickName(loginUser.getNickName());
        if(StrUtil.isNotEmpty(loginUser.getHeadImage())){
            loginUserVO.setHeadImage(downUrl + loginUser.getHeadImage());
        }
        return Result.ok(loginUserVO);
    }

    @Override
    public boolean exit(String token) {
        return tokenService.deleteLoginUser(token);
    }

    @Override
    public Result<UserDetailVO> detail() {
        Long userId = ThreadLocalUtils.get(SecurityConstants.USER_ID,Long.class);
        if(userId == null){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User user = userCacheManager.getUserById(userId);
        if(BeanUtil.isEmpty(user)){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }

        // 用户信息 肯定拿到了
        UserDetailVO userDetailVO = BeanUtil.copyProperties(user, UserDetailVO.class);

        if(StrUtil.isNotEmpty(userDetailVO.getHeadImage())){
            userDetailVO.setHeadImage(downUrl + userDetailVO.getHeadImage());
        }

        return Result.ok(userDetailVO);
    }



    @Override
    public int edit(UserDetailVO userDetailVO) {
        User user = checkUser();
        userDetailVO.setHeadImage(user.getHeadImage());
        BeanUtil.copyProperties(userDetailVO, user);

        refreshCache(user);

        return userMapper.updateById(user);
    }


    @Override
    public int updateHeadImage(String headImage) {
        User user = checkUser();

        user.setHeadImage(headImage);
        refreshCache(user);
        return userMapper.updateById(user);
    }

    // 缓存刷新
    private void refreshCache(User user) {
        userCacheManager.refreshUser(user);
        tokenService.refreshLoginUser(user.getNickName(),
                user.getHeadImage(),ThreadLocalUtils.get(Constants.USER_KEY,String.class));
    }


    private User checkUser() {
        Long userId = ThreadLocalUtils.get(SecurityConstants.USER_ID,Long.class);
        if(userId == null){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        User user = userMapper.selectById(userId);
        if(BeanUtil.isEmpty(user)){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        return user;
    }

    //    获取 redis 中 codeTime （次数） 的key
    public String getPhoneCodeTimeKey(String phone) {
        return CacheConstants.PHONE_CODE_TIMES_KEY+phone;
    }


//    获取 redis 中 code 的key
    public String getPhoneCodeKey(String phone) {
        return CacheConstants.PHONE_CODE_KEY+phone;
    }

//    检查 手机号格式
    public  boolean checkPhone(String phone) {
        Pattern regex = Pattern.compile("^1[2|3|4|5|6|7|8|9][0-9]\\d{8}$");
        Matcher m = regex.matcher(phone);
        return m.matches();
    }

}
